﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Threading;

namespace GameBoard
{
    [DataContract]
    public class TerrainViewModel : INotifyPropertyChanged
    {
        public enum ShapeMode
        {
            None,
            Sphere,
            Cone,
            Line,
            Square,
            ObjectFile
        }

        private string _currentObjectFile;
        private bool _heightEnabled;
        private string _objectPlacementImageFile;

        private ObservableCollection<BaseTerrainData> _placedVisualsList
            = new ObservableCollection<BaseTerrainData>();

        private int _shapeHeightValue = 20;
        private bool _shapeIsPermanent;
        private bool _shapeIsTransparent;
        private ShapeMode _shapeSelection;
        private int _shapeSizeValue = 20;
        private int _shapeWidthValue = 20;

        [DataMember] private List<BaseVisualFactory> _terrainVisualFactories = new List<BaseVisualFactory>();
        private bool _widthEnabled;


        /// <summary>
        ///     Objects like placed cones, cubes, and spheres. Permanent terrain objects.
        /// </summary>
        public List<BaseVisualFactory> TerrainVisualFactories
        {
            get { return _terrainVisualFactories; }
            set { _terrainVisualFactories = value; }
        }

        //The data that is accessible in the view - exists solely to provide an observable collection
        public ObservableCollection<BaseTerrainData> PlacedVisualsList
        {
            get { return _placedVisualsList; }
            set { _placedVisualsList = value; }
        }

        public ShapeMode ShapePlacementType
        {
            get { return _shapeSelection; }
            set
            {
                _shapeSelection = value;
                VisualsViewModel.Instance.SetMouseCursorToAoEFor(value);
                if (value == ShapeMode.None)
                {
                    VisualsViewModel.Instance.ClearShapePreview();
                }
                EnableFieldsByType(ShapePlacementType);
            }
        }

        public Brush ShapeBrush 
        {
            get
            {
                return new SolidColorBrush(VisualsViewModel.Instance.SelectedTeamColor);
            }
        }

        private void EnableFieldsByType(ShapeMode shapePlacementType)
        {
            if (shapePlacementType == ShapeMode.Square)
            {
                HeightEnabled = true;
            }
            else
            {
                HeightEnabled = false;
            }
            if(shapePlacementType==ShapeMode.Square)
            {
                WidthEnabled = true;
                HeightEnabled = true;
            }
            else
            {
                WidthEnabled = false;
                HeightEnabled = false;
            }
        }


        public bool ShapeIsPermanent
        {
            get { return _shapeIsPermanent; }
            set
            {
                _shapeIsPermanent = value;
                OnPropertyChanged("ShapeIsPermanent");
            }
        }


        public bool ShapeIsTransparent
        {
            get { return _shapeIsTransparent; }
            set
            {
                _shapeIsTransparent = value;
                OnPropertyChanged("ShapeIsTransparent");
            }
        }

        public string ObjectPlacementImageFile
        {
            get { return _objectPlacementImageFile; }
            set
            {
                _objectPlacementImageFile = value;
                OnPropertyChanged("ObjectPlacementImageFile");
                OnPropertyChanged("ShapeBrush");
                ShapeImageSelected = !string.IsNullOrWhiteSpace(ObjectPlacementImageFile);
            }
        }

        private bool _shapeImageSelected;
        public bool ShapeImageSelected
        {
            get { return _shapeImageSelected; }
            set
            {
                _shapeImageSelected = value;
                OnPropertyChanged("ShapeImageSelected");
            }
        }



        public int ShapeSizeValue
        {
            get { return _shapeSizeValue; }
            set
            {
                _shapeSizeValue = value;
                OnPropertyChanged("ShapeSizeValue");
            }
        }

        public bool HeightEnabled
        {
            get { return _heightEnabled; }
            set
            {
                _heightEnabled = value;
                OnPropertyChanged("HeightEnabled");
            }
        }

        public bool WidthEnabled
        {
            get { return _widthEnabled; }
            set
            {
                _widthEnabled = value;
                OnPropertyChanged("WidthEnabled");
            }
        }

        public int ShapeWidthValue
        {
            get { return _shapeWidthValue; }
            set
            {
                _shapeWidthValue = value;
                OnPropertyChanged("ShapeWidthValue");
            }
        }

        public int ShapeHeightValue
        {
            get { return _shapeHeightValue; }
            set
            {
                _shapeHeightValue = value;
                OnPropertyChanged("ShapeHeightValue");
            }
        }

        public string CurrentObjectFile
        {
            get { return _currentObjectFile; }
            set
            {
                _currentObjectFile = value;
                OnPropertyChanged("CurrentObjectFile");
            }
        }

        public MapExtensionVisualFactory ExtraMapSpace { get; set; }

        public MapVisualFactory TheMapFactory { get; set; }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged(string name)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        #endregion

        internal void RemoveTerrainVisualData(Guid guid)
        {
            BaseVisualFactory match = TerrainVisualFactories.First(x => x.FactoryVisualID == guid);
            TerrainVisualFactories.Remove(match);
            BaseTerrainData dataMatch = PlacedVisualsList.First(x => x.FactoryID == guid);
            PlacedVisualsList.Remove(dataMatch);
            OnTerrainFactoryRegistrationChanged(
                new TerrainFactoryRegistrationChangedEventArgs(match.FactoryVisualID,
                                                               dataMatch.CenterOfTerrainFeature,
                                                               TerrainFactoryRegistrationChangedEventArgs
                                                                   .RegistrationType.Deregistered)
                );
        }

        internal void PopulateBoardWithTerrain(GameBoardVisual gameBoardVisual)
        {
            foreach (BaseVisualFactory cur in TerrainVisualFactories)
            {
                gameBoardVisual.AddVisual(cur.FactoryVisualID, cur.MakeVisual());
            }
        }

        internal BaseTerrainData GetTerrainDataFor(Guid guid)
        {
            BaseVisualData matching = TerrainVisualFactories.First(x => x.FactoryVisualID == guid).VisualData;
            return matching as BaseTerrainData;
        }


        internal void RegisterFactory(BaseVisualFactory newFactory)
        {
            TerrainVisualFactories.Add(newFactory);
            var terrainData = newFactory.VisualData as BaseTerrainData;
            PlacedVisualsList.Add(terrainData);
            OnTerrainFactoryRegistrationChanged(
                new TerrainFactoryRegistrationChangedEventArgs(newFactory.FactoryVisualID,
                                                               terrainData.CenterOfTerrainFeature,
                                                               TerrainFactoryRegistrationChangedEventArgs
                                                                   .RegistrationType.Registered)
                );
        }

        internal void ClearViewModel()
        {
            PlacedVisualsList.Clear();
            TerrainVisualFactories.Clear();
        }

        public event EventHandler<TerrainFactoryRegistrationChangedEventArgs> TerrainFactoryRegistrationChanged;

        protected virtual void OnTerrainFactoryRegistrationChanged(TerrainFactoryRegistrationChangedEventArgs e)
        {
            EventHandler<TerrainFactoryRegistrationChangedEventArgs> handler = TerrainFactoryRegistrationChanged;
            if (handler != null)
            {
                handler(this, e);
            }
        }

        internal void PopulateFromViewModel(TerrainViewModel terrainViewModel)
        {
            foreach (BaseVisualFactory cur in terrainViewModel.TerrainVisualFactories)
            {
                RegisterFactory(cur);
            }
        }

        /// <param name="gbv">
        ///     It's presumed that there one game board visual is the same as any other
        ///     Containing the same terrain objects
        /// </param>
        internal Point3D FindGroundPoint(Point3D paramPoint,
                                         TerrainViewModel terrainViewModel, double visualLargestSide,
                                         GameBoardVisual gbv)
        {
            var vs = new VisualIntersections();


            var mapChildren = gbv.Viewport.Children.OfType<MapVisual>();

            var mapIntersections = vs.FindIntersections(mapChildren, paramPoint);

            List<Visual3D> placedTerrainChildren = PlacedTerrainObjects(gbv, terrainViewModel);

            var terrainIntersections = vs.FindIntersections(placedTerrainChildren, paramPoint);


            //There really should only be one.
            var firstMapHit = mapIntersections.FirstOrDefault();
            var best = new Point3D(paramPoint.X, paramPoint.Y, double.MinValue);
            if (terrainIntersections.Any())
            {
                //Find the closest to the hit that isn't a massive jump above the character's height
                foreach (var cur in terrainIntersections)
                {
                    double distaway = Math.Abs(cur.PointHit.Z - paramPoint.Z);
                    if (cur.PointHit.Z > best.Z && distaway <= visualLargestSide*3)
                    {
                        best.Z = cur.PointHit.Z;
                    }
                }
            }
            //We could have a map hit and a best terrain hit
            if (terrainIntersections.Any() && firstMapHit !=null)
            {
                if (best.Z > firstMapHit.PointHit.Z)
                {
                    return best;
                }
                return firstMapHit.PointHit;
            }

            //We could have only a map hit
            if (firstMapHit!=null)
            {
                return firstMapHit.PointHit;
            }

            //We could have only a terrain hit
            if (terrainIntersections.Any())
            {
                return best;
            }
            return paramPoint;
        }


        
        private List<Visual3D> PlacedTerrainObjects(GameBoardVisual gbv, TerrainViewModel terrainViewModel)
        {
            var ret = gbv.Viewport.Children.OfType<ITerrainFeature>().OfType<IVisual>();
            ret = ret.Where(x => !x.BelongsToMoveablePicture && !(x is MapVisual));
            return ret.OfType<Visual3D>().ToList();
        }

        internal void SelectedBrushChanged()
        {
            OnPropertyChanged("ShapeBrush");
        }

        
        /// <summary>
        /// This complicated doodad is intended to keep track of the original state of the terrain, prior to the highlighting
        /// </summary>
        Dictionary<DispatcherTimer, Tuple<Guid, MemoryStream>> _selectedTerrainObjects = new Dictionary<DispatcherTimer, Tuple<Guid, MemoryStream>>();
        public void HighlightTerrain(Guid guid, object boardTypes)
        {
            if (_selectedTerrainObjects.Any(x => x.Value.Item1.Equals(guid)))
            {
                return;
            }
            var ser = new DataContractSerializer(typeof(BaseTerrainData), null, int.MaxValue, false, true, null);
            BaseTerrainData matchingData = GetTerrainDataFor(guid);
            var ms = new MemoryStream();
            ser.WriteObject(ms, matchingData);
            matchingData.MaterialFile = "";
            matchingData.MaterialColor = Color.FromRgb
                (
                    (byte)(byte.MaxValue - matchingData.MaterialColor.R),
                    (byte)(byte.MaxValue - matchingData.MaterialColor.G),
                    (byte)(byte.MaxValue - matchingData.MaterialColor.B));


            DispatcherTimer dt = new DispatcherTimer();
            dt.Tick += DtOnTick;
            dt.Interval = TimeSpan.FromSeconds(2);
                        dt.Start();
            _selectedTerrainObjects.Add(dt, new Tuple<Guid, MemoryStream>(guid, ms));
            ResetVisual(guid);
        }

        private void ResetVisual(Guid guid)
        {
            VisualsViewModel.Instance.RemoveVisuals(guid);
            var factory = TerrainVisualFactories.First(x => x.FactoryVisualID.Equals(guid));
            VisualsViewModel.Instance.AddVisuals(factory);
        }

        private void DtOnTick(object sender, EventArgs e)
        {
            var curTimer = (DispatcherTimer) sender;
            curTimer.Stop();
            var curItems = _selectedTerrainObjects[curTimer];
            var ser = new DataContractSerializer(typeof(BaseTerrainData));
            curItems.Item2.Position = 0;
            var oldData = ser.ReadObject(curItems.Item2) as BaseTerrainData;
            var factory = TerrainVisualFactories.FirstOrDefault(x => x.FactoryVisualID.Equals(curItems.Item1));
            if (factory != null)
            {
                factory.VisualData = oldData;
                ResetVisual(factory.FactoryVisualID);    
            }
            _selectedTerrainObjects.Remove(curTimer);
            curTimer = null;
        }
    }
}